package ksv.cron.v1.impl;

import ksv.cron.CronContext;
import ksv.cron.CronCst;
import ksv.cron.bean.SysCron;
import ksv.cron.v1.ICron;
import lombok.Getter;
import lombok.Setter;
import org.springframework.scheduling.support.CronSequenceGenerator;

import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

/**
 * @创建人 wsdc
 * @时间 2021/1/31
 * @描述 系统默认的实现
 */
@Setter
@Getter
public class DefaultICronImpl implements ICron {
    public static final DateTimeFormatter L_D_T_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    private SysCron sysCron;
    private CronContext currentContext;
    private CronSequenceGenerator cronSequenceGenerator;
    private LocalDateTime callTime;
    private String status = CronCst.CronTimeStatus.NORMAL;

    public DefaultICronImpl(SysCron sysCron) {
        this.sysCron = sysCron;
        cronSequenceGenerator = new CronSequenceGenerator(sysCron.getCronExpression());

        //  生成此时的上下文信息
        LocalDateTime localDateTime = nextDate();

        //  如果后面没有有效日期了 检查上一次是否距离现在不超过45s  兼容时间差距
        //  考虑到定时任务 频率是会超过1分钟的 我们是30s定时去加载数据的，如果是单次的那么就存在错过的问题
        //  这里暂时这样处理 后续再行优化
        if(localDateTime == null){
            localDateTime = nextDate(LocalDateTime.now().plusSeconds(-45));
            status = CronCst.CronTimeStatus.BEFORE;
        }

        //  如果没有计划生效日期 并且没有强制执行的标记
        if(localDateTime == null){
            localDateTime = LocalDateTime.now();
            status = CronCst.CronTimeStatus.INVALID;
        }

        currentContext = new CronContext();
        currentContext.setICron(this);
        currentContext.setUniqueKey(getClass().getSimpleName()+"@@@@"+sysCron.getId()+"="+L_D_T_FORMAT.format(localDateTime));
        callTime = localDateTime;

        //  这里一定会生成上下文 至于这个上下文是否有效 由钩子函数来决定是否去执行
        //  钩子函数自行扩展 status 来判定
    }

    @Override
    public SysCron sysCron() {
        return sysCron;
    }

    @Override
    public CronContext nextContext() {
        LocalDateTime localDateTime = nextDate();
        if(localDateTime == null){
            return null;
        }

        CronContext context = new CronContext();
        context.setICron(this);
        context.setUniqueKey(sysCron.getId()+""+L_D_T_FORMAT.format(localDateTime));

        return context;
    }

    @Override
    public CronContext currentContext() {
        return currentContext;
    }

    @Override
    public CronContext next() {
        LocalDateTime localDateTime = nextDate(callTime);
        if(localDateTime == null){
            currentContext = null;
            return currentContext;
        }

        //  重新生成上下文参数
        currentContext = new CronContext();
        currentContext.setICron(this);
        currentContext.setUniqueKey(getClass().getSimpleName()+"@@@@"+sysCron.getId()+"="+L_D_T_FORMAT.format(localDateTime));

        callTime = localDateTime;

        return currentContext;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        //  距离当前时间还有多少时间才会执行
        LocalDateTime now = LocalDateTime.now();
        long second = Duration.between(now, callTime).getSeconds();
        long convert = unit.convert(second, TimeUnit.SECONDS);
        return convert;
    }

    @Override
    public int compareTo(Delayed o) {
        ICron o1 = (ICron) o;
        long seconds = Duration.between(o1.callTime(), callTime).getSeconds();
        return (int) seconds;
    }

    @Override
    public LocalDateTime callTime() {
        return callTime;
    }

    public LocalDateTime nextDate(){
        return nextDate(LocalDateTime.now());
    }

    public LocalDateTime nextDate(LocalDateTime current){
        //  注意 这里月份需要减少1
        Calendar calendar = new GregorianCalendar(current.getYear(),current.getMonthValue() -1,current.getDayOfMonth(),current.getHour(),current.getMinute(),current.getSecond());

        Date date = calendar.getTime();
        Date next = cronSequenceGenerator.next(date);
        if(next == null){
            return null;
        }

        Instant instant = next.toInstant();
        LocalDateTime rtn = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()).plusSeconds(sysCron.getSecond());

        //  超过360视为无效
        if(Duration.between(current,rtn).getSeconds() > 360 * 24 * 60 * 60l){
            return null;
        }
        return rtn;
    }

    @Override
    public String status() {
        return status;
    }
}
